#include "pch.h"
#include "DropShadowEffect.h"

using namespace Dx;
using namespace XamlFx;
using namespace Windows::UI::Xaml;
using namespace Microsoft::WRL;
using namespace Windows::Graphics::Display;

DEPENDENCY_PROPERTY_IMP_ONCHANGE(DropShadowEffect, Radius,        double, 0.0, OnRadiusChanged)
DEPENDENCY_PROPERTY_IMP_ONCHANGE(DropShadowEffect, ShadowDepth,   double, 0.0, OnShadowDepthChanged)
DEPENDENCY_PROPERTY_IMP_ONCHANGE(DropShadowEffect, Direction,     double, 0.0, OnDirectionChanged)
DEPENDENCY_PROPERTY_IMP_ONCHANGE(DropShadowEffect, ShadowColor,   Windows::UI::Color, Windows::UI::Colors::Black, OnShadowColorChanged)

DropShadowEffect::DropShadowEffect()
{
    SetPadding();
}

DropShadowEffect::~DropShadowEffect()
{
}

void DropShadowEffect::OnShadowColorChanged(xaml::DependencyObject^ d, xaml::DependencyPropertyChangedEventArgs^ e)
{
   auto effect = safe_cast<DropShadowEffect^>(d);

   if(effect->m_dropShadowEffect)
   {
      effect->SetColor();
      effect->SetPadding();
      effect->Invalidate();
   }
}

void DropShadowEffect::OnRadiusChanged(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)
{
   auto effect = safe_cast<DropShadowEffect^>(d);
   auto radius = safe_cast<double>(e->NewValue);

   if(effect->m_dropShadowEffect)
   {
      effect->m_dropShadowEffect->SetValue(D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, static_cast<float>(radius));
      effect->SetPadding();
      effect->Invalidate();
   }
}

void DropShadowEffect::OnShadowDepthChanged(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)
{
    auto effect = safe_cast<DropShadowEffect^>(d);

    if (effect->m_affineTransformEffect)
    {
        effect->SetPadding();
        effect->Invalidate();
    }
}

void DropShadowEffect::OnDirectionChanged(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)
{
    auto effect = safe_cast<DropShadowEffect^>(d);

    if (effect->m_affineTransformEffect)
    {
        effect->SetPadding();
        effect->Invalidate();
    }
}

void DropShadowEffect::SetPadding()
{
    float effectGrowthPadding = static_cast<float>(Radius) * 3.0f;

    Dx::Point2F offset(0.0f, 0.0f);
    float shadowDepth = static_cast<float>(ShadowDepth);

    float angle = static_cast<float>(Direction) * static_cast<float>(M_PI) / 180.0f;

    offset.X = (shadowDepth * cos(angle));
    offset.Y = (shadowDepth * sin(angle));

    if (m_affineTransformEffect)
    {
        auto matrix = D2D1::Matrix3x2F::Translation(offset.X, offset.Y);
        m_affineTransformEffect->SetValue(D2D1_2DAFFINETRANSFORM_PROP_TRANSFORM_MATRIX, matrix);
    }
   
    Padding = Thickness(effectGrowthPadding + (offset.X < 0.0f ? abs(offset.X) : 0.0f), 
                        effectGrowthPadding + (offset.Y < 0.0f ? abs(offset.Y) : 0.0f), 
                        effectGrowthPadding + (offset.X > 0.0f ? abs(offset.X) : 0.0f), 
                        effectGrowthPadding + (offset.Y > 0.0f ? abs(offset.Y) : 0.0f));
}

void DropShadowEffect::SetColor()
{
   auto color = safe_cast<Windows::UI::Color>(ShadowColor);
   m_dropShadowEffect->SetValue(D2D1_SHADOW_PROP_COLOR, D2D1::Vector4F(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, color.A / 255.0f));
}

auto DropShadowEffect::GetEffect(d2d::Bitmap const& input) -> d2d::Effect
{
    m_dropShadowEffect.SetInput(0, input);
    m_affineTransformEffect.SetInputEffect(0, m_dropShadowEffect);

    m_compositeEffect.SetInputEffect(0, m_affineTransformEffect);
    m_compositeEffect.SetInput(1, input);
    return m_compositeEffect;
}

void DropShadowEffect::OnInitialized()
{
    m_compositeEffect       = CreateEffect(CLSID_D2D1Composite);
    m_dropShadowEffect      = CreateEffect(CLSID_D2D1Shadow);
    m_affineTransformEffect = CreateEffect(CLSID_D2D12DAffineTransform);

    m_dropShadowEffect->SetValue(D2D1_SHADOW_PROP_OPTIMIZATION, D2D1_DIRECTIONALBLUR_OPTIMIZATION_BALANCED);
    m_dropShadowEffect->SetValue(D2D1_SHADOW_PROP_BLUR_STANDARD_DEVIATION, static_cast<float>(Radius));

    SetColor();
    SetPadding();
}